#+---------------------------------------------+
#| ProxyBot 0.7
#+---------------------------------------------+
#| Written by Alec Hussey
#| Copyright (C) 2008 MadSoft
#| Website: www.madsoft.org
#| License: GNU General Public License v3
#+---------------------------------------------+

from config import *
from socket import *
import time, botlib, threading

class Scanner(threading.Thread):
	def __init__(self, proxybot):
		threading.Thread.__init__(self)
		
		# Define all global class variables
		self.proxybot = proxybot
		self.args = proxybot.args
		self.username = proxybot.username
		self.hostname = proxybot.hostname
		
		# Check validity of IP address
		try:
			# Split up IP or self.hostname by period
			self.iprange = self.args[0].split(".")
			if not self.iprange[0].isdigit() and not self.iprange[1].isdigit():
				# Attempt to resolve IP address by hostname
				try:
					self.iprange = gethostbyname(self.args[0]).split(".")
				except gaierror:
					self.proxybot.send_message("Failed to resolve network address.")
					return
			
				"""
				Once we have the resolved IP address, we strip invalid characters
				and set a range of one
				"""
				self.ipstart = int(self.iprange[3].strip(self.proxybot.invalid_chars))
				self.iprange_r = range(1)
			elif len(self.iprange[3].split(",")) == 2:
				"""
				Since we know that this is probably a valid IP address where the user
				has specified a range of IPs to scan, split the string by the comma in
				the last number of the IP where the user will have define their range
				then strip all invalid characters and add one to ipstop in order to make
				sure that all IPs in the range are scanned
				"""
				self.ipstart = int(self.iprange[3].split(",")[0].strip(self.proxybot.invalid_chars))
				self.ipstop = int(self.iprange[3].split(",")[1].strip(self.proxybot.invalid_chars)) + 1
				self.iprange_r = range(self.ipstart, self.ipstop)
			else:
				"""
				Since this is just a standard (single) IP address, we simply take out 
				all invalid characters and set a range of one
				"""
				self.ipstart = int(self.iprange[3].strip(self.proxybot.invalid_chars))
				self.iprange_r = range(1)
		except IndexError:
			raise RuntimeError, "Invalid IP address."
		
		# Split port range up by the comma
		self.portrange = self.args[1].split(",")
		if len(self.portrange) == 2:
			"""
			Since we know that the user has requested a scan on a range of ports we
			define the start and stop ports by taking the first and second elements
			of the port range and clean and increment them; we increment portstop to
			ensure that all ports are scanned in the range
			"""
			self.portstart = int(self.portrange[0].strip(self.proxybot.invalid_chars))
			self.portstop = int(self.portrange[1].strip(self.proxybot.invalid_chars)) + 1
			self.portrange_r = range(self.portstart, self.portstop)
		else:
			"""
			Since the user has only define a sigle port to scan just define and clean
			the start port and set the range to one
			"""
			self.portstart = int(self.portrange[0].strip(self.proxybot.invalid_chars))
			self.portrange_r = range(1)
	
	def run(self):
		# Start port scan on ranges
		self.proxybot.send_message("Scanning...")
		
		# Log the scan to the database
		self.proxybot.db.cursor.execute("INSERT INTO scans(username, hostname, iprange, portrange, time) VALUES(%s, %s, %s, %s, %s);", \
		                               (self.username, self.hostname, self.args[0], self.args[1], time.ctime()))
		
		# Loop through each IP in range
		for ipnum in self.iprange_r:
			# Loop through each port in range
			for portnum in self.portrange_r:
				# Create proper IP and or port numbers depending on the range(s)
				if len(self.iprange_r) == 1:
					ipnum = self.ipstart
				if len(self.portrange_r) == 1:
					portnum = self.portstart
				
				# Concatonate the IP address back together and check status
				ip = "%s.%s.%s.%d" % (self.iprange[0], self.iprange[1], self.iprange[2], ipnum)
				status = (u"9Open" if self.__portcheck(ip, portnum) else u"4Closed")
				
				# Send results to the client whom requested them
				self.proxybot.send_message("IP: %s Port: %d Status: %s" % (ip, portnum, status))
				
				# If the current port on that IP is open, log it to the database
				if status == u"9Open":
					# Check database for duplicate entry
					self.proxybot.db.cursor.execute("SELECT * FROM ports WHERE ip=%s AND port=%s;", (ip, portnum))
					
					# If no results were returned, then add new entry to database
					if len(self.proxybot.db.cursor.fetchall()) == 0:
						self.proxybot.db.cursor.execute("INSERT INTO ports(ip, port, time) VALUES(%s, %s, %s);", (ip, portnum, time.ctime()))
				
				# Delay next message by one second
				#time.sleep(1)
		
		# Finish up
		self.proxybot.db.cursor.execute("COMMIT;")
		self.proxybot.send_message("Done!")
	
	def __portcheck(self, ipaddress, port):
		# Test to see if a connection can be made
		# Create new socket to attempt connection on TCP
		try:
			sock = socket(AF_INET, SOCK_STREAM)
			sock.settimeout(1.0)
			sock.connect((ipaddress, port))
			sock.close()
			return True
		except:
			return False
		
		# Create new socket to attempt connection on UDP
		try:
			sock = socket(AF_INET, SOCK_DGRAM)
			sock.settimeout(1.0)
			sock.connect((ipaddress, port))
			sock.close()
			return True
		except:
			return False
